package com.ljt.yupictureddd.domain.picture.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ljt.yupictureddd.infrastracture.manager.upload.FilePictureUpload;
import com.ljt.yupictureddd.infrastracture.manager.upload.PictureUploadTemplate;
import com.ljt.yupictureddd.infrastracture.manager.upload.UrlPictureUpload;
import com.ljt.yupictureddd.infrastracture.manager.upload.dto.file.UploadPictureResult;
import com.ljt.yupictureddd.domain.space.entity.Space;
import com.ljt.yupictureddd.application.service.SpaceApplicationService;
import com.ljt.yupictureddd.domain.picture.entity.Picture;
import com.ljt.yupictureddd.domain.picture.repository.PictureRepository;
import com.ljt.yupictureddd.domain.picture.service.PictureDomainService;
import com.ljt.yupictureddd.domain.picture.valueobject.PictureReviewStatusEnums;
import com.ljt.yupictureddd.domain.user.entity.User;
import com.ljt.yupictureddd.domain.user.service.UserDomainService;
import com.ljt.yupictureddd.infrastracture.api.CosManager;
import com.ljt.yupictureddd.infrastracture.api.aliyunai.AliYunAiApi;
import com.ljt.yupictureddd.infrastracture.api.aliyunai.model.CreateOutPaintingTaskRequest;
import com.ljt.yupictureddd.infrastracture.api.aliyunai.model.CreateOutPaintingTaskResponse;
import com.ljt.yupictureddd.infrastracture.exception.BusinessException;
import com.ljt.yupictureddd.infrastracture.exception.ErrorCode;
import com.ljt.yupictureddd.infrastracture.exception.ThrowUtils;
import com.ljt.yupictureddd.infrastracture.utils.ColorSimilarUtils;
import com.ljt.yupictureddd.infrastracture.utils.ColorTransformUtils;
import com.ljt.yupictureddd.interfaces.dto.picture.*;
import com.ljt.yupictureddd.interfaces.vo.picture.PictureVO;
import com.ljt.yupictureddd.interfaces.vo.user.UserVO;
import lombok.extern.slf4j.Slf4j;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.support.TransactionTemplate;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.awt.*;
import java.io.IOException;
import java.util.List;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author 六件套
 * @description 针对表【picture(图片)】的数据库操作Service实现
 * @createDate 2025-03-07 19:43:57
 */
@Service
@Slf4j
public class PictureDomainServiceImpl implements PictureDomainService {

    @Resource
    private UserDomainService userDomainService;
    @Resource
    private FilePictureUpload filePictureUpload;
    @Resource
    private UrlPictureUpload urlPictureUpload;
    @Resource
    private CosManager cosManager;
    @Resource
    private SpaceApplicationService spaceApplicationService;
    @Resource
    private TransactionTemplate transactionTemplate;
    @Resource
    private AliYunAiApi aliYunAiApi;
    @Resource
    private PictureRepository pictureRepository;

    /**
     * @param pictureUploadRequest
     * @param loginUser            当前登陆的用户
     * @return
     */
    @Override
    public PictureVO uploadPicture(Object inputSource, PictureUploadRequest pictureUploadRequest, User loginUser) {
//        1.检验参数
//        没有登陆
        ThrowUtils.throwIf(loginUser == null, ErrorCode.NOT_LOGIN_ERROR);
//         校验空间是否存在
        Long spaceId = pictureUploadRequest.getSpaceId();
        if (spaceId != null) {
            Space space = spaceApplicationService.getById(spaceId);
            // 空间不存在，抛出异常
            ThrowUtils.throwIf(ObjUtil.isNull(space), ErrorCode.NOT_FOUND_ERROR, "空间不存在");
            // 空间存在，但是不是自己的空间，也要抛出异常
            Long userId = space.getUserId();
            ThrowUtils.throwIf(ObjUtil.notEqual(userId, loginUser.getId()), ErrorCode.NO_AUTH_ERROR, "不是自己的空间");
            // 拿到了空间,校验额度够不够
            ThrowUtils.throwIf(space.getTotalCount() + 1 >= space.getMaxSize(), ErrorCode.OPERATION_ERROR, "空间条数不足");
            // 校验大小有没有超过最大
            ThrowUtils.throwIf(space.getTotalSize() >= space.getMaxSize(), ErrorCode.PARAMS_ERROR, "空间大小不足");
        }


//        判断是还新增还是修改
        Long pictureId = null;
        if (pictureUploadRequest != null) {
            pictureId = pictureUploadRequest.getId();
        }
        Picture oldPicture = null;
//        如果是更新,需要判断数据库中有没有
        if (pictureId != null) {
            oldPicture = pictureRepository.getById(pictureId);
            ThrowUtils.throwIf(ObjUtil.isNull(oldPicture), ErrorCode.NOT_FOUND_ERROR, "图片不存在");
//        仅有本人和管理员可以编辑图片
            ThrowUtils.throwIf(ObjUtil.notEqual(oldPicture.getUserId(), loginUser.getId()), ErrorCode.NO_AUTH_ERROR);
            // 更新也需要校验一下空间，传的空间和之前保存在数据库中的空间id是否一致
            if (ObjUtil.isNull(spaceId)) {
                // 如果并没有传入空间id，那么就是传入公共图库的。或者是就是没传入。
                // 解决方法就是：复用数据库的空间id
                spaceId = oldPicture.getSpaceId();
            }
            ThrowUtils.throwIf(ObjUtil.notEqual(spaceId, oldPicture.getSpaceId()), ErrorCode.PARAMS_ERROR, "不是自己的空间");

        }


//        2.上传图片
//        封装一下
//        这里用public是因为是公有的图库
        String uploadPathPrefix;
        if (spaceId != null) {
            // 空间id不为空，传入了私有空间
            uploadPathPrefix = String.format("space/%s", spaceId);
        } else {
            // 这个就是公共图库
            uploadPathPrefix = String.format("public/%s", loginUser.getId());
        }
        // 默认是文件啊上传
        PictureUploadTemplate template = filePictureUpload;
        // 如果是url字符串，那就变成url上传。（模板方法设计模式）
        if (inputSource instanceof String) {
            template = urlPictureUpload;
        }

//        UploadPictureResult uploadPictureResult = fileManager.uploadPicture(multipartFile, uploadPathPrefix);
        UploadPictureResult uploadPictureResult = template.uploadPicture(inputSource, uploadPathPrefix);

//        3.构造一下要入数据库的信息
        Picture picture = new Picture();
        picture.setSpaceId(spaceId);
        picture.setUrl(uploadPictureResult.getUrl());
        // 同时设置缩略图
        picture.setThumbnailUrl(uploadPictureResult.getThumbnailUrl());
        String picName = uploadPictureResult.getPicName();
        if (ObjUtil.isNotNull(pictureUploadRequest) && StrUtil.isNotBlank(pictureUploadRequest.getPictureName())) {
            picName = pictureUploadRequest.getPictureName();
        }
        picture.setName(picName);
        picture.setPicSize(uploadPictureResult.getPicSize());
        picture.setPicWidth(uploadPictureResult.getPicWidth());
        picture.setPicHeight(uploadPictureResult.getPicHeight());
        picture.setPicScale(uploadPictureResult.getPicScale());
        picture.setPicFormat(uploadPictureResult.getPicFormat());
        picture.setPicColor(ColorTransformUtils.getStandardColor(uploadPictureResult.getPicColor()));
        picture.setUserId(loginUser.getId());


//        如果是更新
        if (pictureId != null) {
            picture.setId(pictureId);
            picture.setEditTime(new Date());
        }
//        补充审核参数
        this.fillReviewParams(picture, loginUser);
//         操作数据库:需要更新或保存,更新空间余额,所以需要用到事务
        Long finalSpaceId = spaceId;
        transactionTemplate.execute(status -> {
//        4.操作数据库了
            // 更新图片或者保存图片
            boolean result = pictureRepository.saveOrUpdate(picture);
            ThrowUtils.throwIf(!result, ErrorCode.SYSTEM_ERROR);
            // 更新空间信息
            spaceApplicationService.lambdaUpdate()
                    .eq(Space::getId, finalSpaceId)
                    .setSql("totalSize = totalSize + "+ picture.getPicSize())// 牺牲了精确度,但是让业务代码好写了
                    .setSql("totalCount = totalCount + 1")// 精确的东西需要很多很多事务....
            ;
            return null;
        });
        // 如果是更新，可以清除旧图片了。
        if (pictureId != null) {
            this.clearPictureFile(oldPicture);
        }

        return PictureVO.objToVO(picture);
    }

    /**
     * 填充审核参数
     *
     * @param picture
     * @param loginUser
     */
    @Override
    public void fillReviewParams(Picture picture, User loginUser) {
        if (loginUser.isAdmin()) {
            picture.setReviewStatus(PictureReviewStatusEnums.PASS.getValue());
            picture.setReviewMessage("管理员自动过审");
            picture.setReviewerId(loginUser.getId());
            picture.setReviewTime(new Date());
        } else {
//            不是管理员的话,用到这个方法,审核状态重置
//            无论编辑还是新建
            picture.setReviewStatus(PictureReviewStatusEnums.REVIEWING.getValue());
        }

    }

    @Override
    public QueryWrapper<Picture> getQueryWrapper(PictureQueryRequest request) {
        ThrowUtils.throwIf(request == null, ErrorCode.PARAMS_ERROR, "请求参数为空");

        Long id = request.getId();
        String name = request.getName();
        String introduction = request.getIntroduction();
        String category = request.getCategory();
        List<String> tags = request.getTags();
        Long picSize = request.getPicSize();
        Integer picWidth = request.getPicWidth();
        Integer picHeight = request.getPicHeight();
        Double picScale = request.getPicScale();
        String picFormat = request.getPicFormat();
        String searchText = request.getSearchText();
        Long userId = request.getUserId();
        String sortField = request.getSortField();
        String sortOrder = request.getSortOrder();

//        审核信息
        Integer reviewStatus = request.getReviewStatus();
        String reviewMessage = request.getReviewMessage();
        Long reviewerId = request.getReviewerId();

        Long spaceId = request.getSpaceId();
        boolean nullSpaceId = request.isNullSpaceId();

        Date startEditTime = request.getStartEditTime();
        Date endEditTime = request.getEndEditTime();


        QueryWrapper<Picture> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq(ObjUtil.isNotNull(id), "id", id);
        queryWrapper.like(StrUtil.isNotBlank(name), "name", name);
        queryWrapper.like(StrUtil.isNotBlank(introduction), "introduction", introduction);
        queryWrapper.like(StrUtil.isNotBlank(reviewMessage), "reviewMessage", reviewMessage);
        queryWrapper.eq(StrUtil.isNotBlank(category), "category", category);
        queryWrapper.eq(ObjUtil.isNotNull(picSize), "picSize", picSize);
        queryWrapper.eq(ObjUtil.isNotNull(picWidth), "picWidth", picWidth);
        queryWrapper.eq(ObjUtil.isNotNull(picHeight), "picHeight", picHeight);
        queryWrapper.eq(ObjUtil.isNotNull(picHeight), "picHeight", picHeight);
        queryWrapper.eq(ObjUtil.isNotNull(spaceId), "spaceId", spaceId);
        queryWrapper.eq(StrUtil.isNotBlank(picFormat), "picFormat", picFormat);
        queryWrapper.eq(ObjUtil.isNotNull(userId), "userId", userId);
        queryWrapper.eq(ObjUtil.isNotNull(reviewStatus), "reviewStatus", reviewStatus);
        queryWrapper.eq(ObjUtil.isNotNull(reviewerId), "reviewerId", reviewerId);

        queryWrapper.isNull(nullSpaceId, "spaceId");

        queryWrapper.gt(ObjUtil.isNotNull(startEditTime), "editTime", startEditTime);
        queryWrapper.lt(ObjUtil.isNotNull(endEditTime), "editTime", endEditTime);

//        两个难处理的字段
        if (StrUtil.isNotBlank(searchText)) {
//            需要拼接 and ( name like '%xxx%' or introduction like '%xxx%'
            queryWrapper.and(q -> {
                q.like("name", searchText).or().like("introduction", searchText);
            });
        }

        if (CollUtil.isNotEmpty(tags)) {
//            需要拼接:
//            and ( tag like '%\'java1\'%' )
//            and ( tag like '%\'python\'%')
            for (String tag : tags) {
                queryWrapper.like("tag", "\'" + tag + "\'");
            }
        }
//        最后补充排序
        queryWrapper.orderBy(StrUtil.isNotEmpty(sortField), sortOrder.equals("ascend"), sortField);
        return queryWrapper;
    }


    /**
     * 由分页的picture对象查出分页的PictureVO对象(列表查列表)
     *
     * @param page    分页的Picture对象
     * @param request
     * @return
     */
    @Override
    public Page<PictureVO> getPictureVOList(Page<Picture> page, HttpServletRequest request) {
        Page<PictureVO> pictureVOPage = new Page<>(page.getCurrent(), page.getSize(), page.getTotal());
        List<Picture> pictureList = page.getRecords();
        if (CollUtil.isEmpty(pictureList)) {
            return pictureVOPage;
        }
//        对象列表 => 封装对象列表
        List<PictureVO> pictureVOList = pictureList.stream().map(PictureVO::objToVO).collect(Collectors.toList());
//        把所有的UserId收集起来,然后发一次数据库请求查出来,查一个整体,再用代码把数据库记录一条一条的塞回给PictureVOList
        Set<Long> userIdSet = pictureList.stream().map(Picture::getUserId).collect(Collectors.toSet());
        Map<Long, List<UserVO>> userVOListMap = userDomainService.listByIds(userIdSet).stream()
                .map(userDomainService::getUserVO)
                .collect(Collectors.groupingBy(UserVO::getId));// 这个map的泛型是因为用了groupingBy的原因,List都是只有一个元素的List
//        一条一条塞回去
        pictureVOList.forEach(p -> {
            Long userId = p.getUserId();
            if (userVOListMap.containsKey(userId)) {
                p.setUserVO(userVOListMap.get(userId).get(0));
            }
        });
//        大功告成
        pictureVOPage.setRecords(pictureVOList);
        return pictureVOPage;
    }

    /**
     * 校验图片
     *
     * @param picture
     */
    @Override
    public void validPicture(Picture picture) {
        ThrowUtils.throwIf(picture == null, ErrorCode.PARAMS_ERROR);

        Long id = picture.getId();
        String url = picture.getUrl();
        String introduction = picture.getIntroduction();

//        前端传输id了,id不能是空的，增删改查中：“改”，要求id不为空
        ThrowUtils.throwIf(ObjUtil.isNull(id), ErrorCode.PARAMS_ERROR, "id不能为空");

//        传输了url才检验
        if (StrUtil.isNotBlank(url)) {
            ThrowUtils.throwIf(url.length() > 1024, ErrorCode.PARAMS_ERROR, "url过长");
        }
//        穿了introduction才检验
        if (StrUtil.isNotBlank(introduction)) {
            ThrowUtils.throwIf(introduction.length() > 800, ErrorCode.PARAMS_ERROR, "简介过于长");
        }
//        我觉得这个方法是看了原型文档和接口文档可以设计出来的一个通用方法
    }

    /**
     * 审核图片
     */
    @Override
    public void doPictureReview(PictureReviewRequest pictureReviewRequest, User loginUser) {
//        校验参数(转换成po还需校验)
        ThrowUtils.throwIf(ObjUtil.isNotNull(pictureReviewRequest), ErrorCode.PARAMS_ERROR, "参数为空");
        Long id = pictureReviewRequest.getId();
        Integer reviewStatus = pictureReviewRequest.getReviewStatus();
        String reviewMessage = pictureReviewRequest.getReviewMessage();
//        状态怕传了任意值
        PictureReviewStatusEnums pictureReviewStatusEnums = PictureReviewStatusEnums.getEnumByValue(reviewStatus);
        ThrowUtils.throwIf(ObjUtil.hasNull(id, pictureReviewStatusEnums, reviewMessage), ErrorCode.PARAMS_ERROR, "参数错误");
        ThrowUtils.throwIf(ObjUtil.equals(PictureReviewStatusEnums.REVIEWING, pictureReviewStatusEnums), ErrorCode.PARAMS_ERROR, "不允许把图片改为待审核");
//        校验存在
        Picture oldPicture = pictureRepository.getById(id);
        ThrowUtils.throwIf(ObjUtil.isNull(oldPicture), ErrorCode.NOT_FOUND_ERROR);
//        校验审核状态是否重复,已经改状态
        ThrowUtils.throwIf(ObjUtil.equals(oldPicture.getReviewStatus(), pictureReviewStatusEnums.getValue()), ErrorCode.PARAMS_ERROR, "审核状态重复");
//        数据库操作(操作前,继续填充字段)
        Picture picture = BeanUtil.copyProperties(pictureReviewRequest, Picture.class);
        picture.setReviewerId(loginUser.getId());
        picture.setReviewTime(new Date());
        boolean update = pictureRepository.updateById(picture);
        ThrowUtils.throwIf(!update, ErrorCode.OPERATION_ERROR);
    }

    /**
     * @return
     */
    @Override
    public Integer uploadPictureByBatch(PictureUploadByBatchRequest pictureUploadByBatchRequest, User loginUser) {
        // 校验参数
        String searchText = pictureUploadByBatchRequest.getSearchText();
        Integer count = pictureUploadByBatchRequest.getCount();
        String namePrefix = pictureUploadByBatchRequest.getNamePrefix();

        ThrowUtils.throwIf(count > 30, ErrorCode.PARAMS_ERROR, "最多30条");
        // 抓取内容
        String fetchUrl = String.format("https://cn.bing.com/images/async?q=%s&mmasync=1", searchText);
        Document document = null;
        try {
            document = Jsoup.connect(fetchUrl).get();
        } catch (IOException e) {
            log.error("获取页面失败：", e);
            throw new BusinessException(ErrorCode.OPERATION_ERROR, "获取页面失败");
        }
        Element div = document.getElementsByClass("dgControl").first();
        ThrowUtils.throwIf(ObjUtil.isNull(div), ErrorCode.OPERATION_ERROR, "获取元素失败");
        Elements imgList = div.select("img.mimg");
        // 遍历元素，依次上传图片
        int uploadCount = 0;
        for (Element imgElement : imgList) {
            String fileUrl = imgElement.attr("src");
            if (StrUtil.isBlank(fileUrl)) {
                log.info("当前链接为空，已经跳过：{}", fileUrl);
                continue;
            }
            // 处理文件地址，防止符号需要转义或者对象存储冲突的问题
            // 比如地址中有很多& ? =这些符号
            int questionMarkIndex = fileUrl.indexOf("?");
            if (questionMarkIndex > -1) {
                fileUrl = fileUrl.substring(0, questionMarkIndex);
            }
            // 上传文件
            PictureUploadRequest pictureUploadRequest = new PictureUploadRequest();
            pictureUploadRequest.setUrl(fileUrl);
            pictureUploadRequest.setPictureName(namePrefix + StrUtil.padPre(String.valueOf(uploadCount + 1), 4, '0'));

            try {
                PictureVO pictureVO = this.uploadPicture(fileUrl, pictureUploadRequest, loginUser);
                log.info("图片上传成功,id={}", pictureVO.getId());
                uploadCount++;
            } catch (Exception e) {
                log.error("图片上传失败,", e);
                continue;
            }

            if (uploadCount > count) {
                break;
            }
        }
        return uploadCount;
    }

    /**
     * 清理图片文件
     *
     * @param oldPicture
     */
    @Async
    @Override
    public void clearPictureFile(Picture oldPicture) {
        if (ObjUtil.isNull(oldPicture)) {
            return;
        }
        // 没有实现秒传，所以图片没有被多条记录使用（因为腾讯云COS没有快捷方式那种）
        // 判断图片是否被多条记录使用
        String pictureUrl = oldPicture.getUrl();
        String thumbnailUrl = oldPicture.getThumbnailUrl();
        Long count = pictureRepository.lambdaQuery().eq(Picture::getUrl, pictureUrl)
                .count();
        // 被多条记录引用了，不能清理
        if (count > 1) {
            return;
        }
        // 删除图片
        cosManager.deleteObject(pictureUrl);
        // 删除缩略图
        if (StrUtil.isNotBlank(thumbnailUrl)) {
            cosManager.deleteObject(thumbnailUrl);
        }
    }

    /**
     * 校验图片权限(现在是有私人空间后的校验啦)
     *
     * @param picture
     * @param loginUser
     */
    @Override
    public void checkPictureAuth(Picture picture, User loginUser) {
        Long spaceId = picture.getSpaceId();
        Long loginUserId = loginUser.getId();
        // 公共图库的图片，管理员和本人可以进行CRUD
        if (ObjUtil.isNull(spaceId)) {
            // 既不是管理员，又不是本人，抛异常
            ThrowUtils.throwIf(!loginUser.isAdmin() && ObjUtil.notEqual(picture.getUserId(), loginUserId), ErrorCode.NO_AUTH_ERROR);
        }
        // 私人空间的图片，只有本人可以CRUD
        else {
            // 只要不是本人
            ThrowUtils.throwIf(ObjUtil.notEqual(picture.getUserId(), loginUserId), ErrorCode.NO_AUTH_ERROR);
        }
    }

    /**
     * 删除图片
     *
     * @param id
     * @param loginUser
     */
    @Override
    public void deletePicture(Long id, User loginUser) {
        Picture picture = pictureRepository.getById(id);
        ThrowUtils.throwIf(ObjUtil.isNull(picture), ErrorCode.NOT_FOUND_ERROR);
        // 校验权限
        this.checkPictureAuth(picture, loginUser);
        transactionTemplate.execute(status -> {
//        4.操作数据库了
//        操作数据库
            // 删除旧图片
            boolean result = pictureRepository.removeById(id);
            ThrowUtils.throwIf(!result, ErrorCode.OPERATION_ERROR);
            // 更新空间信息
            spaceApplicationService.lambdaUpdate()
                    .eq(Space::getId, picture.getSpaceId())
                    .setSql("totalSize = totalSize + ", picture.getPicSize())// 牺牲了精确度,但是让业务代码好写了
                    .setSql("totalCount = totalCount + 1")// 精确的东西需要很多很多事务....
            ;
            return null;
        });
        this.clearPictureFile(picture);
    }

    /**
     * 编辑图片
     *
     * @param pictureEditRequest
     * @param loginUser
     */
    @Override
    public void editPicture(PictureEditRequest pictureEditRequest, User loginUser) {
        Picture picture = BeanUtil.copyProperties(pictureEditRequest, Picture.class);
        picture.setTags(JSONUtil.toJsonStr(pictureEditRequest.getTags()));// 转换一下
        this.validPicture(picture);
//        校验数据库存在？ =>
        Long id = pictureEditRequest.getId();
        Picture oldPicture = pictureRepository.getById(id);
        ThrowUtils.throwIf(ObjUtil.isNull(oldPicture), ErrorCode.NOT_FOUND_ERROR);
        // 校验权限
        this.checkPictureAuth(oldPicture, loginUser);
//        补充审核参数
        this.fillReviewParams(picture, loginUser);
//        0.操作数据库前,检查参数
        picture.setEditTime(new Date());
//        1.操作数据库
        boolean update = pictureRepository.updateById(picture);
        ThrowUtils.throwIf(!update, ErrorCode.OPERATION_ERROR);
    }

    /**
     * 根据色调查询当前空间下面的图片
     *
     * @param spaceId
     * @param picColor
     * @param loginUser
     * @return
     */
    @Override
    public List<PictureVO> searchPictureByColor(Long spaceId, String picColor, User loginUser) {
        // 校验参数
        // 1.空校验
        ThrowUtils.throwIf(ObjUtil.isNull(spaceId) || StrUtil.isBlank(picColor), ErrorCode.PARAMS_ERROR, "参数为空");
        ThrowUtils.throwIf(ObjUtil.isNull(loginUser), ErrorCode.NO_AUTH_ERROR, "没登录");
        // 校验权限
        // 1.是不是本人空间
        Space space = spaceApplicationService.getById(spaceId);
        ThrowUtils.throwIf(ObjUtil.notEqual(space.getUserId(), loginUser.getId()), ErrorCode.NO_AUTH_ERROR, "个人私有空间错误");
        // 校验存在
        // 业务逻辑
        // 1.取出空间所有图片(并且主色调字段不能为空)
        List<Picture> pictureList = pictureRepository.lambdaQuery()
                .eq(Picture::getSpaceId, space.getId())
                .isNotNull(Picture::getPicColor)
                .list();
        // 1.1如果没有图片返回空列表
        if (CollUtil.isEmpty(pictureList)) {
            return new ArrayList<>();
        }
        // 2.计算出计算图片的相似度,并且排序
        Color decodedSearchColor = Color.decode(picColor);
        // 这里使用stream流
        List<PictureVO> pictureVOList = pictureList.stream()
                // 计算相似度进行排序
                .sorted(Comparator.comparingDouble(p -> {
                    String hexPicColor = p.getPicColor();
                    if (StrUtil.isBlank(hexPicColor)) {
                        return Double.MAX_VALUE;
                    }
                    // 解码16进制成Color对象
                    Color decodedPicColor = Color.decode(hexPicColor);
                    // 这里调用工具类计算相似度,返回数值越大,相似度越高.但是我需要按照高相似度的图片在前面展示.所以应该是降序.
                    // comparator默认是从小到大,加个负号就能从大到小
                    return -ColorSimilarUtils.calculateSimilarity(decodedPicColor, decodedSearchColor);
                }))
                .limit(20)
                // 转换成vo,因为本身就是自己的空间,就不用picturesevice的getPictureVO方法.不用多此一举
                .map(PictureVO::objToVO)
                .collect(Collectors.toList());

        // 3.按相似度返回结果(要返回封装类)
        return pictureVOList;
    }

    /**
     * 批量编辑图片
     * @param pictureEditByBatchRequest
     * @param loginUser
     */
    @Override
    public void editPictureByBatch(PictureEditByBatchRequest pictureEditByBatchRequest, User loginUser){
        // 校验参数
        List<Long> pictureIdList = pictureEditByBatchRequest.getPictureIdList();
        Long spaceId = pictureEditByBatchRequest.getSpaceId();
        String category = pictureEditByBatchRequest.getCategory();
        List<String> tags = pictureEditByBatchRequest.getTags();

        ThrowUtils.throwIf(CollUtil.isEmpty(pictureIdList),ErrorCode.PARAMS_ERROR);
        ThrowUtils.throwIf(ObjUtil.isNull(spaceId),ErrorCode.PARAMS_ERROR);
        ThrowUtils.throwIf(StrUtil.isBlank(category),ErrorCode.PARAMS_ERROR);

        // 校验权限
        // 1.如果不是本人的空间,抛出异常
        // 查询空间
        Space space = spaceApplicationService.getById(spaceId);
        // 空间不存在,抛出异常
        ThrowUtils.throwIf(Objects.isNull(space), ErrorCode.NOT_FOUND_ERROR);
        // 校验是否是本人的空间:不是本人的空间,抛出异常
        ThrowUtils.throwIf(ObjUtil.notEqual(space.getUserId(),loginUser.getId()),ErrorCode.NO_AUTH_ERROR);

        // 校验存在
        // 1.查询指定图片(仅仅选择需要的字段)
        List<Picture> pictureList = pictureRepository.lambdaQuery()
                .select(Picture::getId, Picture::getSpaceId)
                .eq(Picture::getSpaceId, spaceId)
                .in(Picture::getId, pictureIdList)
                .list();
        // 如果为空,直接不用做了
        if (CollUtil.isEmpty(pictureList)) return;
        // 业务逻辑
        // 1.更新分类和标签
        pictureList.forEach( p ->{
            // 做一个兼容,只要穿了对应的字段才更新
            if(StrUtil.isNotBlank(category)){
                p.setCategory(category);
            }
            if (CollUtil.isNotEmpty(tags)){
                p.setTags(JSONUtil.toJsonStr(tags));
            }
        });
        // 2.批量重命名
        String nameRule = pictureEditByBatchRequest.getNameRule();
        fillPictureWithNameRule(pictureList,nameRule);
        // 操作数据库
        boolean updated = pictureRepository.updateBatchById(pictureList);
        ThrowUtils.throwIf(!updated,ErrorCode.OPERATION_ERROR,"批量编辑失败");
    }

    /**
     * 列表填充命名规则
     * @param pictureList
     * @param nameRule 格式:图片{序号}
     */
    private void fillPictureWithNameRule(List<Picture> pictureList, String nameRule) {
        if (StrUtil.isBlank(nameRule) || CollUtil.isEmpty(pictureList)) return;
        long count = 1;
        try {
            for (Picture picture : pictureList) {
                String pictureName = nameRule.replaceAll("\\{序号}", String.valueOf(count++));
                picture.setName(pictureName);
            }
        } catch (Exception e) {
            log.error("名称解析错误",e);
            throw new BusinessException(ErrorCode.OPERATION_ERROR);
        }
    }

    /**
     * 创建扩图请求
     *
     * @return
     */
    @Override
    public CreateOutPaintingTaskResponse createOutPaintingTask(CreatePictureOutPaintingTaskRequest createPictureOutPaintingTaskRequest, User loginUser) {
        // 校验参数
        ThrowUtils.throwIf(ObjUtil.isNull(createPictureOutPaintingTaskRequest),ErrorCode.PARAMS_ERROR);

        // 校验存在
        // Picture picture = this.getById(createPictureOutPaintingTaskRequest.getPictureId());
        // ThrowUtils.throwIf(ObjUtil.isNull(picture),ErrorCode.NOT_FOUND_ERROR);
        Picture picture = Optional.ofNullable(pictureRepository.getById(createPictureOutPaintingTaskRequest.getPictureId()))
                .orElseThrow(() -> new BusinessException(ErrorCode.NOT_FOUND_ERROR));
        // 校验权限
        checkPictureAuth(picture,loginUser);
        // 业务逻辑
        // 构建对象
        CreateOutPaintingTaskRequest.Input input = new CreateOutPaintingTaskRequest.Input();
        input.setImageUrl(picture.getUrl());

        CreateOutPaintingTaskRequest request = new CreateOutPaintingTaskRequest();
        request.setInput(input);
        request.setParameters(createPictureOutPaintingTaskRequest.getParameters());

        return aliYunAiApi.createOutPaintingTaskRequest(request);
        // 操作数据库
    }
}




